r2d3 is an R package that allows the user to generate D3 visualizations with R

Section 1: Why R and D3?

The D3 JavaScript library

  • Data Driven Documents
  • Industry standard for creating interactive graphics
  • Used for creating custom interactive data visualizations

The r2d3 R Package

  • Allows the programmer to pass data from R to a JavaScript file, and for that JavaScript file to render a graphic in R

Use r2d3 (d3.js) for complete control over your map’s appearance

Section 2: When should I use r2d3 for my maps?

r2d3 should be used when generating interactive, highly specialized maps

Section 2: When should I use r2d3 for my maps?

For creating static maps in R

  • ggplot2 if possible
  • Use r2d3 only if making a map requiring more customization

For creating interactive graphics

  • Use leaflet in R or JavaScript if possible
  • Advantages of r2d3 over the leaflet library
  • D3 better for data viz, adaptability, less traditional mapping

r2d3 interactive graphics can be shared in any format that can run JavaScript

Section 2: When should I use r2d3 for my maps?

D3 graphics can be displayed in HTML documents

  • Website
  • Shiny App
  • R markdown, quarto, or Shiny HTML document (i.e. flexdashboard R package)

D3 graphics are by default SVG graphics

  • Easier for 508 compliance

R to D3

Section 3: Basics of r2d3

Suppose you need to display a map of median home values for Pierce County, WA, but need to quickly see different levels of geographic aggregation within the county:

  • Build an interactive map which can easily be toggled between geographic levels
  • Include tooltips with GEOID and an estimate for median home value
library(tidyverse)
library(tidycensus)
library(sf)
library(r2d3)

From Data to Map

Section 3: Basics of r2d3

Get data at the census tract and block group level using tidycensus1

map_data_cbg <- tidycensus::get_acs(geography = "cbg",
                                    variables = "B25077_001",
                                    state = "WA",
                                    county = "Pierce",
                                    geometry = TRUE) %>%
  dplyr::select(GEOID, estimate, geometry) %>%
  dplyr::mutate(level = "cbg")

map_data_tract <- tidycensus::get_acs(geography = "tract",
                                      variables = "B25077_001",
                                      state = "WA",
                                      county = "Pierce",
                                      geometry = TRUE) %>%
  dplyr::select(GEOID, estimate, geometry) %>%
  dplyr::mutate(level = "tract")

all_data <- bind_rows(map_data_cbg, map_data_tract)

The r2d3() function

Section 3: Basics of r2d3

shiny::selectInput("geogs", "Geography:", choices = c("cbg","tract"))
shiny::selectInput("na_color", "NA color:", choices = c("gray","darkgray","lightgray"))

r2d3::d3Output("choroMap")

output$choroMap <- r2d3::renderD3({
  r2d3::r2d3(data = all_data, 
             script = "d3_map_example.js",
             options = list(na_color = input$na_color,
                            geog = input$geogs,
                            zoom = TRUE))
})

The r2d3() function

Section 3: Basics of r2d3

JavaScript Code

Section 3: Basics of r2d3

// Remove previous svg
svg.selectAll("g").remove();

// Transform data into json-style format
var filteredData = {
  "type": "FeatureCollection",
  "features": data
    .filter(d => d.level === options.geog) // Filter to the selected geography
    .map(d => ({
      "type": "Feature",
      "geometry": d.geometry,
      "properties": {
        "GEOID": d.GEOID,
        "estimate": d.estimate,
        "level": d.level,
      }
    }))
};

// Define the D3 map projection
var my_projection = d3.geoMercator().fitSize([width, height], filteredData);
var path = d3.geoPath().projection(my_projection);

// Create a blue color ramp for our target variable
const colorScale = d3.scaleSequential(d3.interpolateBlues)
  .domain([0, d3.max(filteredData.features, d => d.properties.estimate)]);
  
// Define zoom behavior
var zoom = d3.zoom()
  .scaleExtent([1, 8])
  .on("zoom", zoomed);
// Apply zoom behavior to the SVG
svg.call(zoom);

// Create a group to hold the map paths (this will help with zooming and panning)
var g = svg.append("g");


// Create the map
g.selectAll('path')
  .data(filteredData.features)
  .enter()
  .append('path')
  .attr('d', path)
  .attr('fill', d => {
    // check if estimate is valid, otherwise set to NA color
    return d.properties.estimate != null && !isNaN(d.properties.estimate) 
      ? colorScale(d.properties.estimate) 
      : options.na_color;
  })
  .on("mouseover", function(event, d) {
    // Show the tooltip on mouseover
    tooltip.transition().duration(200).style("opacity", 1);
    tooltip.html("GEOID: " + d.properties.GEOID + "<br>Estimate: " + d.properties.estimate)
      .style("left", (event.pageX + 10) + "px")
      .style("top", (event.pageY - 28) + "px");
    
    d3.select(this)
    .style("stroke", "black")
    .style("stroke-width", 1.5);
  })
  .on("mousemove", function(event) {
    // Move the tooltip with the mouse
    tooltip.style("left", (event.pageX + 10) + "px")
      .style("top", (event.pageY - 28) + "px");
  })
  .on("mouseout", function() {
    // Hide the tooltip on mouseout
    tooltip.transition().duration(500).style("opacity", 0);
    
    d3.select(this).style("stroke", "none");
  });
  
// Add a tooltip div to the body
var tooltip = d3.select("body")
  .append("div")
  .attr("class", "tooltip")
  .style("position", "absolute")
  .style("padding", "8px")
  .style("background-color", "rgba(0, 0, 0, 0.6)")
  .style("color", "#fff")
  .style("border-radius", "4px")
  .style("pointer-events", "none")
  .style("opacity", 0)
  // Make sure tooltip is in front of all other elements
  .style("z-index", "1000");

// Zoom function
function zoomed(event) {
  if (options.zoom === true) {
    g.attr("transform", event.transform);
  }  
}

Raster to Vector

Section 4: Examples

https://livecrimetracker.norc.org

Section 4: Examples

https://dementiadatahub.org

Section 4: Examples